#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "Application.h"

#include "Document.h"
#include "Class.h"
#include "Storage.h"
#include "FileDialog.h"
#include "ObjectTable.h"
#include "ClassManager.h"
#include "ClipBoard.h"
#include "ProgEnv.h"
#include "Buttons.h"
#include "IAC.h"
#include "WindowSystem.h"
#include "Env.h"
#include "ProgressDialog.h"
#include "FileData.h"
#include "Alert_e.h"
#include "Error.h"
#include "Menu.h"
#include "String.h"
#include "Box.h"
#include "MenuBar.h"
#include "System.h"
#include "ET_stdio.h"

Application *gApplication;
ClipBoard   *gClipBoard;
int          gArgc;
char       **gArgv;
const char  *gVersion= "Version 3.0b, 3/1/93";
const char  *gCopyright= "\
\2511987-1989 Dept. for Comp. Science, University of Zurich\n\
\2511989-1993 Information Technology Lab (UBILAB), Union Bank of Switzerland";

//---- ApplErrorHandler --------------------------------------------------------

static void ApplErrorHandler(int level, bool, const char *location,
														const char *msg)
{
	gApplication->DoOnError(level, location, msg);
}

//---- AppRpcConnection --------------------------------------------------------

class AppRpcConnection : public IAC {
	EvtHandler *handler;
public:
	AppRpcConnection(EvtHandler *eh)
		{ handler= eh; }
	int Dispatch(int, const char*, const char*, int, char*&, int&);
};

int AppRpcConnection::Dispatch(int from, const char *req, const char *buf,
											int len, char *&ret, int &retlen)
{
	if (handler)
		handler->ExtCommand(from, (char*)req, (char*)buf, len, ret, retlen);
	return 0;
}

//---- Application -------------------------------------------------------------

NewAbstractMetaImpl(Application,Manager, (T(lastWindowPos),
		TP(mainDocumentType), TP(fileDialog), TS(gVersion), TS(gCopyright),
		TP(gApplication), TP(gClipBoard), TVP(gArgv,gArgc), TP(gClassManager)));

FileDialog *Application::fileDialog;

Application::Application(int ac, char **av, const Symbol &dt)
												: Manager(BaseName(av[0]))
{
	if (gApplication)
		Error("Application", "only one Application !!");

	gApplication= this;
	gArgc= ac;
	gArgv= av;

	mainDocumentType= &dt;

	ETInit(ac, av);
	WindowSystem::WSInit();

	SetErrorHandler(ApplErrorHandler);
	lastWindowPos= Env::GetValue("Document.Position", Point(150, 100));
}

Application::~Application()
{
	if (gApplication == this)
		gApplication= 0;

	SafeDelete(fileDialog);
}

FileDialog *Application::MakeFileDialog()
{
	return new FileDialog;
}

Manager *Application::NewManager(const Symbol &type)
{
	Manager *mp= DoMakeManager(type); // let user make his managers
	if (mp) {
		AddManager(mp);
		mp->Show();
	}
	return mp;
}

void Application::ParseCommandLine(int argc, char **argv)
{
	for (int i= 1; i < argc;) {
		if (argv[i][0] == '-') {
			int n= DoParseOptions(argv[i], argv[i+1]);
			if (n > 0)
				i+= n;
			else
				i++;
		} else
			OpenDocument(argv[i++]);
	}
}

int Application::DoParseOptions(char *arg0, char*)
{
	if (arg0[0] == '-' && arg0[1] == 'E') {
		if (arg0[2] == 'e')
			gProgEnv->Start();
		if (arg0[2] == 'h')
			gProgEnv->ShowInHierarchy(this->IsA());
		return 1;
	}
	return 0;
}

void Application::RemoveManager(Manager *mp)
{
	Manager::RemoveManager(mp);
	if (Size() <= 0 && !IsOpen())
		Quit();
}

const char *Application::ProgramName()
{
	return BaseName(gProgname);
}

Point Application::GetInitialWindowPos()
{
	Point p= lastWindowPos;
	lastWindowPos+= Point(40, 30);
	return p;
}

void Application::About()
{
	ShowAlert(eAlertSun, "%s\n%s\n%s", ProgramName(), gVersion, gCopyright);
}

Command *Application::DoMenuCommand(int cmd)
{
	switch(cmd) {
	case cNEW:
		NewManager(GetMainDocumentType());
		break;
	case cQUIT:
		Quit();
		break;
	case cABOUT:
		About();
		break;
	case cOPEN:
		OpenDocument(0);
		break;
	default:
		Manager::DoMenuCommand(cmd);
		break;
	}
	return gNoChanges;
}

void Application::DoSetupMenu(Menu *m)
{
	Manager::DoSetupMenu(m);
	switch (m->GetId()) {
	case cFILEMENU:
		m->EnableItems(cOPEN, cNEW, cQUIT, cABOUT, 0);
		break;
	}
}

void Application::Control(int i, int part, void *val)
{
	SendDown(i, part, val);
}

void Application::ExtCommand(int from, char *rq, char *args, int len,
														char *&ret, int &retlen)
{
	char req[100], *cp;

	strcpy(req, rq);
	cp= strchr(req, ':');

	if (cp) {
		*cp++= 0;
		
		if (strcmp(req, "document") == 0) {
			Iter next(MakeIterator());
			Manager *first= (Manager*)next();
			if (first)
				first->ExtCommand(from, cp, args, len, ret, retlen);
		} else if (strcmp(req, "application") == 0) {
			if (strcmp(cp, "quit") == 0)
				Quit();
			else if (strcmp(cp, "about") == 0)
				About();
			else if (strcmp(cp, "new") == 0)
				NewManager(GetMainDocumentType());
			else if (strcmp(cp, "open") == 0)
				OpenDocument(args);
			else if (strcmp(cp, "inspect") == 0)
				Inspect();
			ret= "ok";
		}
	}
	gWindowSystem->Update();
}

void Application::Quit()
{
	if (Close())
		gSystem->ExitControl();
}

Manager *Application::DoMakeManager(Symbol type)
{
	return DoMakeDocuments(type);
}

bool Application::OpenDocument(const char *name)
{
	bool rc= FALSE, dodelete= FALSE;
	Data *data;
	Manager *mp;

	if (name == 0) {
		if (fileDialog == 0)
			fileDialog= MakeFileDialog();
		if (fileDialog->ShowInWindow(eFDRead, 0, this) != cIdOk)
			return FALSE;
		name= fileDialog->FileName();
		data= fileDialog->GetData();
	} else {
		data= new FileData(name);
		dodelete= TRUE;
	}

	if (!CanOpen(data)) {
		ShowAlert(eAlertNote, "Cannot handle document @I%s@P (%s)\n",
				data->ShortName(), data->Type().AsString());
	} else {
		mp= DoMakeManager(data->Type()); // let user make his managers
		if (mp) {
			AddManager(mp);
			mp->Show();
			((Document*)mp)->LoadData(data, TRUE);
			gWindowSystem->Update();
			rc= TRUE;
		}
	}
	if (dodelete)
		delete data;
	return rc;
}

bool Application::CanOpen(Data *data)
{
	if (GetMainDocumentType() == cDocTypeUndef)
		return TRUE;
	if (GetMainDocumentType() == data->Type())
		return TRUE;
	if (GetMainDocumentType() == cDocTypeAscii && data->IsAscii())
		return TRUE;
	return FALSE;
}

void Application::MakeInitManager()
{
	if (Size() <= 0)
		NewManager(GetMainDocumentType());
}

int Application::Run()
{
	gRpc= new AppRpcConnection(this);

	gClipBoard= new ClipBoard;
	gProgress= new ProgressImpl;

	ParseCommandLine(gArgc, gArgv);

	MakeInitManager();

	Open();

	gSystem->Control();

	SetErrorHandler(DefaultErrorHandler);

	SafeDelete(gClipBoard);
	SafeDelete(gRpc);
	SafeDelete(gProgress);

	return 0;
}

void Application::DoOnError(int level, const char *location, const char *msg)
{
	static bool inError= FALSE;

	if (level < Env::GetValue("System.IgnoreLevel", cError) || inError)
		return;
	inError= TRUE;

	char *type= "Warning";
	if (level >= cFatal)
		type= "Fatal";
	else if (level >= cSysError)
		type= "SysError";
	else if (level >= cError)
		type= "Error";

	fprintf(stderr, "%s: %s in <%s>: %s\n", ProgramName(), type, location, msg);
	if (level >= cError) {
		int r= ShowAlert(eAlertError, "%s: %s\nin @B%s@B: %s",
					ProgramName(), type, location, msg);
		switch (r) {
		case cIdIgnore:
			break;
		
		case cIdAbort:
			gSystem->Abort();
			break;
		
		case cIdInspect:
			Debug();
			break;
		}
	}
	inError= FALSE;
}

void Application::Debug()
{
	gRpc->SendTo(0, "attachto", form("%s %d", gProgPath, gSystem->GetPid()));
	gSystem->Wait(15000);
	//ShowAlert(eAlertError, "%s: Debugger did not attach\n", ProgramName());
}

//---- obsolete ----------------------------------------------------------------

Document *Application::DoMakeDocuments(Symbol)
{
	return 0;
}

void Application::NewDocument(Symbol)
{
}

//---- external calls of ET++PE ------------------------------------------------

extern "C" int ETPE_Enter(Object *op);

static int callCount;

int ETPE_Enter(Object *op)
{
	int level= callCount++;
	Port *p= GrGetPort();

	if (op == 0)
		op= gApplication;
		
	if (!ObjectTable::PtrIsValid(op)) {
		fprintf(stderr, "ET++PE: 0x%x is not an instance of Object\n", (int)op);
		return 0;
	}

	op->Inspect();

	//---- nested event-loop
	while (callCount > level && !gQuitApp)
		gSystem->InnerLoop();

	if (gQuitApp)
		callCount= 0;
		
	fprintf(stderr, "\nETPE: Exit\n");

	GrSetPort(p);

	return level;
}

int gCallCount(int d)
{
	callCount+= d;
	return callCount;
}

